/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.gwtrpc4j.stream;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gwt.user.client.rpc.GwtTransient;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamReader;
import com.google.gwt.user.server.Base64Utils;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.google.gwt.user.server.rpc.SerializationPolicyProvider;
import com.google.gwt.user.server.rpc.impl.SerializabilityUtil;
import com.google.gwt.user.server.rpc.impl.SerializedInstanceReference;
import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator;

/**
 * For internal use only. Used for server call serialization. This class is
 * carefully matched with the client-side version.
 */
public final class JClientSerializationStreamReader extends
		AbstractSerializationStreamReader {

	private static final char JS_ESCAPE_CHAR = '\\';

	/**
	 * Used to accumulate elements while deserializing array types. The generic
	 * type of the BoundedList will vary from the component type of the array it
	 * is intended to create when the array is of a primitive type.
	 * 
	 * @param <T>
	 *            The type of object used to hold the data in the buffer
	 */
	private static class BoundedList<T> extends LinkedList<T> {
		/**
	 * 
	 */
		private static final long serialVersionUID = 1L;
		private final Class<?> componentType;
		private final int expectedSize;

		public BoundedList(Class<?> componentType, int expectedSize) {
			this.componentType = componentType;
			this.expectedSize = expectedSize;
		}

		@Override
		public boolean add(T o) {
			assert size() < getExpectedSize();
			return super.add(o);
		}

		public Class<?> getComponentType() {
			return componentType;
		}

		public int getExpectedSize() {
			return expectedSize;
		}
	}

	/**
	 * Enumeration used to provided typed instance readers.
	 */
	private enum ValueReader {
		BOOLEAN {
			@Override
			Object readValue(JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readBoolean();
			}
		},
		BYTE {
			@Override
			Object readValue(JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readByte();
			}
		},
		CHAR {
			@Override
			Object readValue(JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readChar();
			}
		},
		DOUBLE {
			@Override
			Object readValue(JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readDouble();
			}
		},
		FLOAT {
			@Override
			Object readValue(JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readFloat();
			}
		},
		INT {
			@Override
			Object readValue(JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readInt();
			}
		},
		LONG {
			@Override
			Object readValue(JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readLong();
			}
		},
		OBJECT {
			@Override
			Object readValue(JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readObject();
			}
		},
		SHORT {
			@Override
			Object readValue(JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readShort();
			}
		},
		STRING {
			@Override
			Object readValue(JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readString();
			}
		};

		abstract Object readValue(JClientSerializationStreamReader stream)
				throws SerializationException;
	}

	/**
	 * Enumeration used to provided typed instance readers for vectors.
	 */
	private enum VectorReader {
		BOOLEAN_VECTOR {
			@Override
			protected Object readSingleValue(
					JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readBoolean();
			}

			@Override
			protected void setSingleValue(Object array, int index, Object value) {
				Array.setBoolean(array, index, (Boolean) value);
			}
		},
		BYTE_VECTOR {
			@Override
			protected Object readSingleValue(
					JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readByte();
			}

			@Override
			protected void setSingleValue(Object array, int index, Object value) {
				Array.setByte(array, index, (Byte) value);
			}
		},
		CHAR_VECTOR {
			@Override
			protected Object readSingleValue(
					JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readChar();
			}

			@Override
			protected void setSingleValue(Object array, int index, Object value) {
				Array.setChar(array, index, (Character) value);
			}
		},
		DOUBLE_VECTOR {
			@Override
			protected Object readSingleValue(
					JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readDouble();
			}

			@Override
			protected void setSingleValue(Object array, int index, Object value) {
				Array.setDouble(array, index, (Double) value);
			}
		},
		FLOAT_VECTOR {
			@Override
			protected Object readSingleValue(
					JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readFloat();
			}

			@Override
			protected void setSingleValue(Object array, int index, Object value) {
				Array.setFloat(array, index, (Float) value);
			}
		},
		INT_VECTOR {
			@Override
			protected Object readSingleValue(
					JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readInt();
			}

			@Override
			protected void setSingleValue(Object array, int index, Object value) {
				Array.setInt(array, index, (Integer) value);
			}
		},
		LONG_VECTOR {
			@Override
			protected Object readSingleValue(
					JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readLong();
			}

			@Override
			protected void setSingleValue(Object array, int index, Object value) {
				Array.setLong(array, index, (Long) value);
			}
		},
		OBJECT_VECTOR {
			@Override
			protected Object readSingleValue(
					JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readObject();
			}

			@Override
			protected void setSingleValue(Object array, int index, Object value) {
				Array.set(array, index, value);
			}
		},
		SHORT_VECTOR {
			@Override
			protected Object readSingleValue(
					JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readShort();
			}

			@Override
			protected void setSingleValue(Object array, int index, Object value) {
				Array.setShort(array, index, (Short) value);
			}
		},
		STRING_VECTOR {
			@Override
			protected Object readSingleValue(
					JClientSerializationStreamReader stream)
					throws SerializationException {
				return stream.readString();
			}

			@Override
			protected void setSingleValue(Object array, int index, Object value) {
				Array.set(array, index, value);
			}
		};

		protected abstract Object readSingleValue(
				JClientSerializationStreamReader stream)
				throws SerializationException;

		protected abstract void setSingleValue(Object array, int index,
				Object value);

		/**
		 * Convert a BoundedList to an array of the correct type. This
		 * implementation consumes the BoundedList.
		 */
		protected Object toArray(Class<?> componentType,
				BoundedList<Object> buffer) throws SerializationException {
			if (buffer.getExpectedSize() != buffer.size()) {
				throw new SerializationException(
						"Inconsistent number of elements received. Received "
								+ buffer.size() + " but expecting "
								+ buffer.getExpectedSize());
			}

			Object arr = Array.newInstance(componentType, buffer.size());

			for (int i = 0, n = buffer.size(); i < n; i++) {
				setSingleValue(arr, i, buffer.removeFirst());
			}

			return arr;
		}

		Object read(JClientSerializationStreamReader stream,
				BoundedList<Object> instance) throws SerializationException {
			for (int i = 0, n = instance.getExpectedSize(); i < n; ++i) {
				instance.add(readSingleValue(stream));
			}

			return toArray(instance.getComponentType(), instance);
		}
	}

	/**
	 * Map of {@link Class} objects to {@link ValueReader}s.
	 */
	private static final Map<Class<?>, ValueReader> CLASS_TO_VALUE_READER = new IdentityHashMap<Class<?>, ValueReader>();

	/**
	 * Map of {@link Class} objects to {@link VectorReader}s.
	 */
	private static final Map<Class<?>, VectorReader> CLASS_TO_VECTOR_READER = new IdentityHashMap<Class<?>, VectorReader>();

	private final ClassLoader classLoader;

	private SerializationPolicy serializationPolicy = RPC
			.getDefaultSerializationPolicy();

	private final SerializationPolicyProvider serializationPolicyProvider;

	/**
	 * Used to look up setter methods of the form 'void Class.setXXX(T value)'
	 * given a Class type and a field name XXX corresponding to a field of type
	 * T.
	 */
	private final Map<Class<?>, Map<String, Method>> settersByClass = new HashMap<Class<?>, Map<String, Method>>();

	protected final List<String> stringTable = new ArrayList<String>();

	protected final ArrayList<String> tokenList = new ArrayList<String>();

	protected List<String> results;
	protected int index;

	{
		CLASS_TO_VECTOR_READER
				.put(boolean[].class, VectorReader.BOOLEAN_VECTOR);
		CLASS_TO_VECTOR_READER.put(byte[].class, VectorReader.BYTE_VECTOR);
		CLASS_TO_VECTOR_READER.put(char[].class, VectorReader.CHAR_VECTOR);
		CLASS_TO_VECTOR_READER.put(double[].class, VectorReader.DOUBLE_VECTOR);
		CLASS_TO_VECTOR_READER.put(float[].class, VectorReader.FLOAT_VECTOR);
		CLASS_TO_VECTOR_READER.put(int[].class, VectorReader.INT_VECTOR);
		CLASS_TO_VECTOR_READER.put(long[].class, VectorReader.LONG_VECTOR);
		CLASS_TO_VECTOR_READER.put(Object[].class, VectorReader.OBJECT_VECTOR);
		CLASS_TO_VECTOR_READER.put(short[].class, VectorReader.SHORT_VECTOR);
		CLASS_TO_VECTOR_READER.put(String[].class, VectorReader.STRING_VECTOR);

		CLASS_TO_VALUE_READER.put(boolean.class, ValueReader.BOOLEAN);
		CLASS_TO_VALUE_READER.put(byte.class, ValueReader.BYTE);
		CLASS_TO_VALUE_READER.put(char.class, ValueReader.CHAR);
		CLASS_TO_VALUE_READER.put(double.class, ValueReader.DOUBLE);
		CLASS_TO_VALUE_READER.put(float.class, ValueReader.FLOAT);
		CLASS_TO_VALUE_READER.put(int.class, ValueReader.INT);
		CLASS_TO_VALUE_READER.put(long.class, ValueReader.LONG);
		CLASS_TO_VALUE_READER.put(Object.class, ValueReader.OBJECT);
		CLASS_TO_VALUE_READER.put(short.class, ValueReader.SHORT);
		CLASS_TO_VALUE_READER.put(String.class, ValueReader.STRING);
	}

	String moduleBaseURL;
	String strongName;

	public JClientSerializationStreamReader(ClassLoader classLoader,
			SerializationPolicyProvider serializationPolicyProvider,
			String moduleBaseURL, String strongName) {
		this.classLoader = classLoader;
		this.serializationPolicyProvider = serializationPolicyProvider;
		this.moduleBaseURL = moduleBaseURL;
		this.strongName = strongName;
	}

	public Object deserializeValue(Class<?> type) throws SerializationException {
		ValueReader valueReader = CLASS_TO_VALUE_READER.get(type);
		if (valueReader != null) {
			return valueReader.readValue(this);
		} else {
			// Arrays of primitive or reference types need to go through
			// readObject.
			return ValueReader.OBJECT.readValue(this);
		}
	}

	public int getNumberOfTokens() {
		return tokenList.size();
	}

	public SerializationPolicy getSerializationPolicy() {
		return serializationPolicy;
	}

	/**
	 * Import from http://gwt-syncproxy.googlecode.com
	 * 
	 * Parse response from GWT RPC example:<br>
	 * [1,["Hello, Hello Simple Object! I am running"],0,5]
	 * 
	 * @param encoded
	 */

	protected List<String> parse(String encoded) {
		List<String> results = new ArrayList<String>();
		encoded = encoded.substring(1, encoded.length() - 1);
		StringBuffer token = new StringBuffer();
		for (int i = 0; i < encoded.length(); i++) {
			char ch = encoded.charAt(i);
			if (ch == ',') {
				results.add(token.toString());
				token = new StringBuffer();
				continue;
			}
			if (ch == '[') {
				int pos = encoded.lastIndexOf(']');
				if (pos < 0) {
				}
				results.add(encoded.substring(i + 1, pos));
				i = pos + 1;
				continue;
			}
			token.append(ch);
		}
		if (token.length() > 0) {
			results.add(token.toString());
		}

		return results;
	}

	@Override
	public void prepareToRead(String encodedTokens)
			throws SerializationException {

		results = parse(encodedTokens);
		index = results.size();
		super.prepareToRead(encodedTokens);

		// Check the RPC version number sent by the client
		if (getVersion() != SERIALIZATION_STREAM_VERSION) {
			throw new IncompatibleRemoteServiceException("Expecting version "
					+ SERIALIZATION_STREAM_VERSION + " from client, got "
					+ getVersion() + ".");
		}

		// Read the type name table
		//
		deserializeStringTable();

		if (serializationPolicyProvider != null) {
			serializationPolicy = serializationPolicyProvider
					.getSerializationPolicy(moduleBaseURL, strongName);

			if (serializationPolicy == null) {
				throw new NullPointerException(
						"serializationPolicyProvider.getSerializationPolicy()");
			}
		}
	}

	public boolean readBoolean() {
		return !results.get(--index).equals("0");
	}

	public byte readByte() {
		return Byte.parseByte(results.get(--index));
	}

	public char readChar() {
		return (char) Integer.parseInt(results.get(--index));
	}

	public double readDouble() {
		return Double.parseDouble(results.get(--index));
	}

	public float readFloat() {
		return Float.parseFloat(results.get(--index));
	}

	public int readInt() {
		return Integer.parseInt(results.get(--index));
	}

	public long readLong() {
		return (long) readDouble() + (long) readDouble();
	}

	public short readShort() {
		return Short.parseShort(results.get(--index));
	}

	public String readString() {
		return getString(readInt());
	}

	@Override
	protected Object deserialize(String typeSignature)
			throws SerializationException {
		Object instance = null;
		try {
			Class<?> instanceClass;
			if (hasFlags(FLAG_ELIDE_TYPE_NAMES)) {
				if (getSerializationPolicy() instanceof TypeNameObfuscator) {
					TypeNameObfuscator obfuscator = (TypeNameObfuscator) getSerializationPolicy();
					String instanceClassName = obfuscator
							.getClassNameForTypeId(typeSignature);
					instanceClass = Class.forName(instanceClassName, false,
							classLoader);
				} else {
					throw new SerializationException(
							"The GWT module was compiled with RPC type name elision enabled, but "
									+ getSerializationPolicy().getClass()
											.getName() + " does not implement "
									+ TypeNameObfuscator.class.getName());
				}
			} else {
				SerializedInstanceReference serializedInstRef = SerializabilityUtil
						.decodeSerializedInstanceReference(typeSignature);
				instanceClass = Class.forName(serializedInstRef.getName(),
						false, classLoader);
				validateTypeVersions(instanceClass, serializedInstRef);
			}

			assert serializationPolicy != null;

			// TODO active the validateDeserialize Policy : read file from the
			// server
			// serializationPolicy.validateDeserialize(instanceClass);

			Class<?> customSerializer = SerializabilityUtil
					.hasCustomFieldSerializer(instanceClass);

			int index = reserveDecodedObjectIndex();

			instance = instantiate(customSerializer, instanceClass);

			rememberDecodedObject(index, instance);

			Object replacement = deserializeImpl(customSerializer,
					instanceClass, instance);

			// It's possible that deserializing an object requires the original
			// proxy
			// object to be replaced.
			if (instance != replacement) {
				rememberDecodedObject(index, replacement);
				instance = replacement;
			}

			return instance;

		} catch (ClassNotFoundException e) {
			throw new SerializationException(e);
		} catch (InstantiationException e) {
			throw new SerializationException(e);
		} catch (IllegalAccessException e) {
			throw new SerializationException(e);
		} catch (IllegalArgumentException e) {
			throw new SerializationException(e);
		} catch (InvocationTargetException e) {
			throw new SerializationException(e.getTargetException());
		} catch (NoSuchMethodException e) {
			throw new SerializationException(e);
		}
	}

	@Override
	protected String getString(int index) {
		if (index == 0) {
			return null;
		}
		// index is 1-based
		assert index > 0;
		assert index <= stringTable.size();
		return stringTable.get(index - 1);
	}

	/**
	 * Deserialize an instance that is an array. Will default to deserializing
	 * as an Object vector if the instance is not a primitive vector.
	 * 
	 * @param instanceClass
	 * @param instance
	 * @throws SerializationException
	 */
	@SuppressWarnings("unchecked")
	private Object deserializeArray(Class<?> instanceClass, Object instance)
			throws SerializationException {
		assert instanceClass.isArray();

		BoundedList<Object> buffer = (BoundedList<Object>) instance;
		VectorReader instanceReader = CLASS_TO_VECTOR_READER.get(instanceClass);
		if (instanceReader != null) {
			return instanceReader.read(this, buffer);
		} else {
			return VectorReader.OBJECT_VECTOR.read(this, buffer);
		}
	}

	private void deserializeClass(Class<?> instanceClass, Object instance)
			throws SerializationException, IllegalAccessException,
			NoSuchMethodException, InvocationTargetException,
			ClassNotFoundException {
		/**
		 * A map from field names to corresponding setter methods. The reference
		 * will be null for classes that do not require special handling for
		 * server-only fields.
		 */
		Map<String, Method> setters = null;

		/**
		 * A list of fields of this class known to the client. If null, assume
		 * the class is not enhanced and don't attempt to deal with server-only
		 * fields.
		 */
		Set<String> clientFieldNames = serializationPolicy
				.getClientFieldNamesForEnhancedClass(instanceClass);
		if (clientFieldNames != null) {
			// Read and set server-only instance fields encoded in the RPC data
			try {
				String encodedData = readString();
				if (encodedData != null) {
					byte[] serializedData = Base64Utils.fromBase64(encodedData);
					ByteArrayInputStream baos = new ByteArrayInputStream(
							serializedData);
					ObjectInputStream ois = new ObjectInputStream(baos);

					int count = ois.readInt();
					for (int i = 0; i < count; i++) {
						String fieldName = (String) ois.readObject();
						Object fieldValue = ois.readObject();
						Field field = instanceClass.getDeclaredField(fieldName);
						field.setAccessible(true);
						field.set(instance, fieldValue);
					}
				}
			} catch (IOException e) {
				throw new SerializationException(e);
			} catch (NoSuchFieldException e) {
				throw new SerializationException(e);
			}

			setters = getSetters(instanceClass);
		}

		Field[] serializableFields = SerializabilityUtil
				.applyFieldSerializationPolicy(instanceClass);
		for (Field declField : serializableFields) {
			assert declField != null;
			if (clientFieldNames != null
					&& !clientFieldNames.contains(declField.getName())) {
				continue;
			}

			Object value = deserializeValue(declField.getType());

			String fieldName = declField.getName();
			Method setter;
			/*
			 * If setters is non-null and there is a setter method for the given
			 * field, call the setter. Otherwise, set the field value directly.
			 * For persistence APIs such as JDO, the setter methods have been
			 * enhanced to manipulate additional object state, causing direct
			 * field writes to fail to update the object state properly.
			 */
			if (setters != null && (setter = setters.get(fieldName)) != null) {
				setter.invoke(instance, value);
			} else {
				boolean isAccessible = declField.isAccessible();
				boolean needsAccessOverride = !isAccessible
						&& !Modifier.isPublic(declField.getModifiers());
				if (needsAccessOverride) {
					// Override access restrictions
					declField.setAccessible(true);
				}

				declField.set(instance, value);
			}
		}

		Class<?> superClass = instanceClass.getSuperclass();
		if (serializationPolicy.shouldDeserializeFields(superClass)) {
			deserializeImpl(SerializabilityUtil
					.hasCustomFieldSerializer(superClass), superClass, instance);
		}
	}

	private Object deserializeImpl(Class<?> customSerializer,
			Class<?> instanceClass, Object instance)
			throws NoSuchMethodException, IllegalArgumentException,
			IllegalAccessException, InvocationTargetException,
			SerializationException, ClassNotFoundException {

		if (customSerializer != null) {
			deserializeWithCustomFieldDeserializer(customSerializer,
					instanceClass, instance);
		} else if (instanceClass.isArray()) {
			instance = deserializeArray(instanceClass, instance);
		} else if (instanceClass.isEnum()) {
			// Enums are deserialized when they are instantiated
		} else {
			deserializeClass(instanceClass, instance);
		}

		return instance;
	}

	// Import from http://gwt-syncproxy.googlecode.com
	protected void deserializeStringTable() throws SerializationException {
		String raw = results.get(--index);
		byte b1;
		byte b2;
		byte b3;
		byte b4;

		boolean startNewString = true;
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < raw.length(); i++) {
			char ch = raw.charAt(i);
			if (startNewString) {
				startNewString = false;
				continue;
			}
			if (ch == '\"') { // end-of-string
				this.stringTable.add(buffer.toString());

				buffer.setLength(0);
				startNewString = true;

				if (i != raw.length() - 1) {
					i++;
				}
				continue;
			}
			if (ch == JS_ESCAPE_CHAR) {
				i++;
				ch = raw.charAt(i);
				switch (ch) {
				case '0': // \0
					buffer.append('\u0000');
					break;
				case 'b': // \b
					buffer.append('\b');
					break;
				case 't': // \t
					buffer.append('\t');
					break;
				case 'n': // \n
					buffer.append('\n');
					break;
				case 'f': // \f
					buffer.append('\f');
					break;
				case 'r': // \r
					buffer.append('\r');
					break;
				case '\"': // \"
					buffer.append('\"');
					break;
				case '\\': // \\
					buffer.append('\\');
					break;
				case 'x': // \\xNN
					b1 = hex2byte(raw.charAt(++i));
					b2 = hex2byte(raw.charAt(++i));
					ch = (char) (b1 * 16 + b2);
					buffer.append(ch);
					break;
				case 'u': // \\uNNNN
					b1 = hex2byte(raw.charAt(++i));
					b2 = hex2byte(raw.charAt(++i));
					b3 = hex2byte(raw.charAt(++i));
					b4 = hex2byte(raw.charAt(++i));
					ch = (char) (b1 * 16 * 16 * 16 + b2 * 16 * 16 + b3 * 16 + b4);
					buffer.append(ch);
					break;
				default:
				}
			} else {
				buffer.append(ch);
			}
		}
	}

	private byte hex2byte(char ch) {
		if ((ch >= '0') && (ch <= '9')) {
			return (byte) (ch - '0');
		}
		if ((ch >= 'A') && (ch <= 'F')) {
			return (byte) (ch - 'A' + 10);
		}
		if ((ch >= 'a') && (ch <= 'f')) {
			return (byte) (ch - 'a' + 10);
		}

		return -1;
	}

	private void deserializeWithCustomFieldDeserializer(
			Class<?> customSerializer, Class<?> instanceClass, Object instance)
			throws NoSuchMethodException, IllegalAccessException,
			InvocationTargetException {
		assert !instanceClass.isArray();

		for (Method method : customSerializer.getMethods()) {
			if ("deserialize".equals(method.getName())) {
				method.invoke(null, this, instance);
				return;
			}
		}
		throw new NoSuchMethodException("deserialize");
	}

	//
	// private String extract() throws SerializationException {
	// try {
	// return tokenList.get(tokenListIndex++);
	// } catch (IndexOutOfBoundsException e) {
	// throw new SerializationException("Too few tokens in RPC request", e);
	// }
	// }

	/**
	 * Returns a Map from a field name to the setter method for that field, for
	 * a given class. The results are computed once for each class and cached.
	 * 
	 * @param instanceClass
	 *            the class to query
	 * @return a Map from Strings to Methods such that the name <code>XXX</code>
	 *         (corresponding to the field <code>T XXX</code>) maps to the
	 *         method <code>void setXXX(T value)</code>, or null if no such
	 *         method exists.
	 */
	private Map<String, Method> getSetters(Class<?> instanceClass) {
		synchronized (settersByClass) {
			Map<String, Method> setters = settersByClass.get(instanceClass);
			if (setters == null) {
				setters = new HashMap<String, Method>();

				// Iterate over each field and locate a suitable setter method
				Field[] fields = instanceClass.getDeclaredFields();
				for (Field field : fields) {
					// Consider non-final, non-static, non-transient (or
					// @GwtTransient) fields only
					if (isNotStaticTransientOrFinal(field)) {
						String fieldName = field.getName();
						String setterName = "set"
								+ Character.toUpperCase(fieldName.charAt(0))
								+ fieldName.substring(1);
						try {
							Method setter = instanceClass.getMethod(setterName,
									field.getType());
							setters.put(fieldName, setter);
						} catch (NoSuchMethodException e) {
							// Just leave this field out of the map
						}
					}
				}

				settersByClass.put(instanceClass, setters);
			}

			return setters;
		}
	}

	static boolean isNotStaticTransientOrFinal(Field field) {
		/*
		 * Only serialize fields that are not static, transient (including
		 * 
		 * @GwtTransient), or final.
		 */
		int fieldModifiers = field.getModifiers();
		return !Modifier.isStatic(fieldModifiers)
				&& !Modifier.isTransient(fieldModifiers)
				&& !field.isAnnotationPresent(GwtTransient.class)
				&& !Modifier.isFinal(fieldModifiers);
	}

	private Object instantiate(Class<?> customSerializer, Class<?> instanceClass)
			throws InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException,
			NoSuchMethodException, SerializationException {
		if (customSerializer != null) {
			for (Method method : customSerializer.getMethods()) {
				if ("instantiate".equals(method.getName())) {
					return method.invoke(null, this);
				}
			}
			// Ok to not have one.
		}

		if (instanceClass.isArray()) {
			int length = readInt();
			// We don't pre-allocate the array; this prevents an allocation
			// attack
			return new BoundedList<Object>(instanceClass.getComponentType(),
					length);
		} else if (instanceClass.isEnum()) {
			Enum<?>[] enumConstants = (Enum[]) instanceClass.getEnumConstants();
			int ordinal = readInt();
			assert ordinal >= 0 && ordinal < enumConstants.length;
			return enumConstants[ordinal];
		} else {
			Constructor<?> constructor = instanceClass.getDeclaredConstructor();
			constructor.setAccessible(true);
			return constructor.newInstance();
		}
	}

	private void validateTypeVersions(Class<?> instanceClass,
			SerializedInstanceReference serializedInstRef)
			throws SerializationException {
		String clientTypeSignature = serializedInstRef.getSignature();
		if (clientTypeSignature.length() == 0) {
			throw new SerializationException("Missing type signature for "
					+ instanceClass.getName());
		}

		String serverTypeSignature = SerializabilityUtil
				.getSerializationSignature(instanceClass, serializationPolicy);

		if (!clientTypeSignature.equals(serverTypeSignature)) {
			throw new SerializationException("Invalid type signature for "
					+ instanceClass.getName());
		}
	}
}
